[JAVA 11] JAVA의 추상 클래스(abstract)와 인터페이스(interface)

[JAVA 11] JAVA의 추상 클래스(abstract)와 인터페이스(interface)

Lecture
Language
태그
language
java
public
완성
Y
생성일
Mar 17, 2024 05:00 PM
LectureName
Java

1. 추상클래스

 
추상클래스의 작성
추상 클래스?
상속이 자손 클래스를 만드는데 조상 클래스를 사용하는 것이라면, 추상 클래스는 기존의 클래스의 공통부분을 봅아내서 조상 클래스를 만드는 것이라고 할 수 있다.
추상 클래스는 추상 메서드를 포함하고 있다는 것을 제외하고는 일반 클래스와 전혀 다르지 않다. 추상클래스에도 생성자가 있으며, 멤버변수와 메서드도 가질 수 있다.
 
추상 클래스 작성 방법
abstract class Unit{ abstract void move(){};
  • 메서드는 선언부와 구현부 두가지 섹션이 존재한다.
  • 추상 메서드는 선언부만 작성하고 구현부는 작성하지 않은 채로 남겨둔 것이다.
 
사용 방법
추상 메서드는 보통 자식 클래스에서 공통적으로 사용하지만 그 메서드가 각각 다른 행동을 해야할떄, 그와 동시에 무조건 작성해야 할 때 사용한다. 오버라이딩을 위해서 그냥 빈 메서드로 남겨둘 수 있지만 자식 메서드에게 꼭 선언하라고 하는것과 같다. 다만 조심할 점은 조상 참조변수로 자식 인스턴스에 접근하지 않으면 해당 메서드를 사용 할 수 없을 수 없기 떄문에 주의해야한다.
Unit[] group = new Unit[4]; group[0] = new Marine(); group[1] = new Tank(); group[2] = new Marine(); gorup[0].move() //가능 Object[] group2 = new Object[4]; group2[0] = new Marine(); group2[1] = new Tank(); group2[2] = new Marine(); group2[0].move() //불가능, Object 클래스에는 move메서드가 정의되어 있지 않기 떄문이다.
 

추상 클래스의 예시

public class BirdLifeGame { public static void main(String[] args) { // TODO Auto-generated method stub } } //새의 추상 클래스 구현 abstract class BirdDefault { int x = 0; int y = 0; String animal_name = "bird"; abstract void FlyTo(int x, int y); } //쿠쿠 클래스 구현 class CuckooBird extends BirdDefault { String name = "cuckoo"; String color = "yellow"; void FlyTo(int x, int y) { this.x += x; this.y += y; } } //독수리 클래스 구현 class Eagle extends BirdDefault { String name = "Eagle"; String color = "black"; void FlyTo(int x, int y) { this.x += x; this.y += y; } }
 

2. 인터페이스(interface)

인터페이스의 의미와 작성
➡️ 의미
인터페이스는 일종의 추상클래스이다. 인터페이스는 추상클래스처럼 추상 메서드를 갖지만 추상클래스보다 추상화 정도가 높아서 추상클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다. 오직 추상메서드와 상수만을 멤버로 가질 수 있으며, 그외의 어떠한 요소도 허용하지 않는다.
 
➡️ 용도
인터페이스는 그 자체만으로 사용되기 보다는 다른 클래스를 작성하는데 도움 줄 목적으로 작성된다.
 
➡️ 작성
인터페이스의 작성은 클래스를 작성하는 것과 같다.
interface 인터페이스 이름{ public static final 타입 상수이름 = 리터럴; public 메서드이름 (매개변수 목록); }
  • 모든 멤버변수는 public static final이여야 하며, 이를 생략할 수 있다.
  • 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다.
→ 생략된 제어자는 컴파일 시에 컴파일러가 자동으로 추가해준다.
 
 
 
인터페이스의 상속
➡️ 인터페이스의 상속
인터페이스는 인터페이스로만 상속받을 수 있으며, 클래스와는 달리 다중상속이 가능하다.
 
➡️ 인터페이스의 다중 상속
기본적으로 JAVA에서는 단일 상속만 허용한다. 하지만 인터페이스를 사용하면 다중 상속처럼 사용할 수 있다. 한개의 클래스를 상속받고 implement를 이용하여 인터페이스를 확장시키면 다중 상속처럼 사용할 수 있다.
//vcr public class VCR{ 어쩌구 저쩌구 } public interface IVCR{ 여기에 VCR 클래스에 정의된 메서드와 일치하는 추상 메서드를 갖는 인터페이스를 작성 } public interface ITVCR{ //두번째 인터페이스 } //implements를 통하여 인터페이스에서 정의된 메서드를 구현하게 시킴 public class MVCR extends VCR implements IVCR, ITVC{ //메서드 구현 }
  • 이렇게 구현한 인터페이스를 확장할 수 있다.
 
 
➡️인터페이스 끼리의 다중 상속
interface Movable{ void move(int x, int y); } interface Attackable{ void attack(Unit u); } interface Fightable extends Movable, Attackable {};
  • 인터페이스의 경우 다중 상속이 가능하다.
  • 다중상속으로 Fightable은 move(), attack() 메서드를 멤버로 갖게 된다.
 
 
인터페이스의 구현
➡️인터페이스의 구현
인터페이스도 추상 클래스처럼 그 자체로는 인스턴스를 생성할 수 없다. 클래스에 확장 시켜주어야 한다.
class 클래스이름 implements 인터페이스 이름{ /*인터페이스 구현해야함*/ }
  • 인터페이스 이름에는 주로 ~를 할 수 있는 의미인 able로 끝나는 경우가 많다.
 
➡️구현 시 주의사항
  1. 만약 구현하는 인터페이스의 메서드중 일부만 구현한다면, abstract를 붙여서 추상 클래스로 선언하여야 한다.
abstract class Fighter implements Fightable{ public void move(int x, int y){} }
 
  1. 클래스에서는 상속과 인터페이스 구현을 둘다 할 수 있다.
class Fighter extends Unit implements Fightable{ public void move(){}; public void attack(){};
 
  1. 인터페이스에 구현된 메서드를 오버라이딩 할 때에는 반드시 인터페이스가 가지고 있는 접근 제어자보다 더 넓은 제어자를 사용해 주어야 한다.
 
 
 
인터페이스의 다형성
➡️개요
인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있으며, 인터페이스 타입으로의 형변환도 가능하다.
 
➡️예시
  1. 인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스 참조
Fightable f = (Fightable)new Figter(); or Fightable f - new Fighter();
 
  1. 메서드의 매개변수 타입으로 사용 가능
void attack(Fighable f){ //method }
  • 메서드 호출 시 해당 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 제공해야 한다는 것이다. 따라서 attack method를 호출할 때에는 매개변수로 Fightable인터페이스를 구현한 클래스의 인스턴스를 넘겨주어야 한다.
 
예제
package teach; public class RepairableTest { public static void main(String[] args) { Tank t = new Tank(); AirPlane a = new AirPlane(); RepairUnit re = new RepairUnit(); re.repair(t); re.repair(a); } } //공통인 요소를 구현 해당 인터페이스를 확장하기 interface Repairable{} //유닛의 공통점 class Unit{ int hitPoint; final int MAX_HP; Unit(int hp){ MAX_HP = hp; } } //지상 객체 class GroundUnit extends Unit{ GroundUnit(int hp){ super(hp); } } //공중 객체 class AirUnit extends Unit{ AirUnit(int hp){ super(hp); } } //지상 - 탱크 class Tank extends GroundUnit implements Repairable{ Tank(){ super(150); hitPoint = MAX_HP; } } //공중 - 비행기 class AirPlane extends AirUnit implements Repairable{ AirPlane(){ super(200); hitPoint = MAX_HP; } } //지상 - 수리병 class RepairUnit extends GroundUnit implements Repairable{ RepairUnit(){ super(10); hitPoint = MAX_HP; } //Repairable 형태, 즉 인터페이스를 매개변수로 사용하였음으로 해당 인터페이스를 확장하여 구현한 클래스를 전 void repair(Repairable r) { if (r instanceof Unit) { Unit u = (Unit)r; while(u.hitPoint==u.MAX_HP){ u.hitPoint++; } System.out.println("repair is finished"); } } }
 
➡️Return type이 인터페이스?
Fighable method(){ Fighter f = new Fighter(); return f; //same as return enw Fighter(); }
  • 리턴 타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다.
 
➡️예제
class c7_5_2{ public static void main(String[] args) { Parseable parser = ParserManager.getParser("XML"); parser.parse("test.xml"); parser = ParserManager.getParser("HTML"); parser.parse("test.html"); } } interface Parseable{ //구문 분석 작업을 수행한다 public abstract void parse(String fileName); } //xml 파서 class XMLParser implements Parseable{ public void parse(String fileName){ System.out.println(fileName + "- XML parsing completed."); } } //html 파서 class HTMLParser implements Parseable{ public void parse(String fileName){ System.out.println(fileName + "- HTML parsing completed."); } } class ParserManager{ public static Parseable getParser(String type){ if(type.equals("XML")){ return new XMLParser(); } else{ Parseable p = new HTMLParser(); return p; // == new HTMLParser(); } } }
 
➡️인터페이스의 장점
  1. 개발 시간을 단축시킬 수 있다.
  1. 표준화가 가능하다.
  1. 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다.
  1. 독립적인 프로그래밍이 가능하다.
 
🙄 Why?
  1. 인터페이스가 작성되면 이를 이용해서 프로그램을 작성하는게 가능하다. 그리고 동시에 다른 한쪽에서는 인터페이스를 구현하는 클래스를 작성하게 하면, 인터페이스를 구현하는 클래스가 작성될때 까지 기다리지 않고 양쪽에서 동시 개발이 가능하다.
  1. 기본 틀을 인터페이스로 구현하면 개발자들의 표준화가 가능하다.
  1. 서로 상속 관계에 있지도 않고 같은 조상클래스를 갖고 있지 않아도 관계를 맺어줄 수 있다.
  1. 한 클래스의 변경이 관련된 다른 클래스에 영향을 미치지 않기 때문에 독립적인 프로그래밍이 가능하다.
 
➡️인터페이스를 사용한 가용성 증진
부모 클래스 building 아래 4가지 건물이 있다고 가정하자
class Factory{} class Apartment{} class House{}
 
해당 클래스 중 아파트와 집에 추가적이며 동일한 메서드를 구현하고 싶다.
interface living{ //method }
 
물론 인터페이스를 구현하여 각자 추가를 해주어도 되지만 클래스를 하나 생성하여 내부적으로 사용하는 방법이 있다.
class Live implements living{ //method 구현 } class Apartment(){ //... live i = new Live(); i.method //메소드 사용 } class House(){ //... live i = new Live(); i.method //메소드 사용 }
 
 
인터페이스의 이해
인터페이스의 작성 방법, 사용방법을 알아 보았다. 매개변수로 사용하여 구현된 클래스를 전달할 수도, 리턴타입을 인터페이스 타입으로 줘서 인터페이스가 구현된 클래스를 반환하는 것 또한 시도해보았다.
이 외에도 인스턴스를 활용하면 의존성 문제를 분리할 수 있다. 사전에 두개의 지식을 염두하고 가야한다.
  • 클래스를 제공하는 쪽과 클레스를 사용하는 쪽이 있다.
  • 클래스를 사용하는 쪽은 사용하려는 변수 혹은 메서드만 알면 된다. (뭐가 구현되어 있는지는 자세하게 알 필요가 없다.)
 
인터페이스를 사용한 관계 분리
클래스B는 db를 관리하는 서버, A는 그 db를 접근할 필요가 있는 시스템이라고 가정하자. 만약 상속을 통하거나 구현의무를 A가 갖게되면 클래스 B에서 db가 변경되면 그에 맞춰서 A도 접근 요소를 바꾸어야 한다. 하지만 Interface를 통하여 필요한 메서드를 구현하고 A에서는 그 메서드만 사용할 수 있도록 설정했다.
interface I{ public abstract void connect(); }; class B implements I{ String dbname= "Mariadb"; public void connect() { System.out.println("connecting.."); } } class A{ void connect(I i) { i.connect(); } }
  • B가 변경되어도 A에서는 connect라는 메서드만 사용하면 되기때문에 의존성 문제가 보다 쉽게 해결됩니다.
 
 
 
Interface에서의 default 메서드와 static 메서드
💡
JDK 1.8부터 인터페이스에 static메서드와 (default) 메서드의 추가를 허용한다.
➡️default 메서드
인터페이스 같은 경우에는 보통 추상 메서드가 추가가 되기 때문에 만약 메서드가 추가되게 된다면 해당 인터페이스를 확장 받는 모든 클래스에 새로운 메서드가 추가가 되어야 한다. 따라서 JDK 설계자들은 default 메서드를 고안해 내었다. 디폴트 메서드는 추상 메서드의 기본적인 구현을 제공하는 메서드로, 추상 메서드가 아니기 떄문에 디폴트 메서드가 새로 추가되어도 해당 인터페이스를 구현한 클래스를 변경하지 않아도 된다.
 
예시)
interface Hello{ default void new Method(); }
 
default method와 중복이 된다면 어떻게 될까
  • 여러 인터페이스의 디폴트 메서드 간의 충돌
→ 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩 해야 한다.
  • 디폴트 메서드와 조상 클래스 메서드 간의 충돌
→ 조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.